package dao

import (
	"context"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/go-xorm/xorm"
	"github.com/mitchellh/mapstructure"
	"github.com/olivere/elastic/v7"

	"im/common"
	"im/dao/es"
	"im/errcode"
	"im/model"
	"im/requests"
	"im/service/orm"
)

const IndexNameOfReplys = "replys"

type Replys interface {
	GetByIds(ids ...int64) (map[int64]model.Replys, error)
	GetById(id int64) (model.Replys, error)
	Insert(p model.Replys) (model.Replys, error)
	Hide(userId int64, id int64) error
	Update(id int64, m model.Replys) error
	ListByPostId(postId int64, pagination requests.Pagination) ([]model.Replys, error)
	SubListByReplyId(replyId int64, pagination requests.Pagination) ([]model.Replys, error)
	AddStat(id int64, statType model.ReplyStatType) error
	SubStat(id int64, statType model.ReplyStatType) error
	// 获取我回复过的相关帖子
	GetReplyedBefore(userId int64) ([]int64, error)
	GetSubReplyListBatch(replyIds ...int64) ([]model.Replys, error)
	GetSubReplCountBatch(replyIds ...int64) ([]CountResp, error)
	GetLikedReplysIdsBatch(userId int64, replyIds ...int64) (map[int64]interface{}, error)
}

func NewReplys() Replys {
	return replys{db: orm.GetDB()}
}

func NewReplysWithEs() Replys {
	return replysWithEs{NewReplys()}
}

type replysWithEs struct {
	dbHandle Replys
}

func (r replysWithEs) GetLikedReplysIdsBatch(userId int64, replyIds ...int64) (map[int64]interface{}, error) {
	return r.dbHandle.GetLikedReplysIdsBatch(userId, replyIds...)
}

func (r replysWithEs) GetSubReplCountBatch(replyIds ...int64) ([]CountResp, error) {
	return r.dbHandle.GetSubReplCountBatch(replyIds...)
}

func (r replysWithEs) GetSubReplyListBatch(replyIds ...int64) ([]model.Replys, error) {
	return r.dbHandle.GetSubReplyListBatch(replyIds...)
}

func (r replysWithEs) GetReplyedBefore(userId int64) ([]int64, error) {
	panic("implement me")
}

func (r replysWithEs) GetByIds(ids ...int64) (map[int64]model.Replys, error) {
	return r.dbHandle.GetByIds(ids...)
}

func (r replysWithEs) GetById(id int64) (model.Replys, error) {
	return r.dbHandle.GetById(id)
}

type EsReplysModel struct {
	model.Replys
	model.TimeModelOfEs
}

func (r replysWithEs) Insert(p model.Replys) (model.Replys, error) {
	m, err := r.dbHandle.Insert(p)
	if err != nil {
		return m, err
	}
	esM := EsReplysModel{
		Replys: m,
		TimeModelOfEs: model.TimeModelOfEs{
			Created: time.Time(m.TimeModel.Created),
			Updated: time.Time(m.TimeModel.Updated),
		},
	}
	return m, es.New().IndexDoc(IndexNameOfReplys, m.Id, esM)
}

func (r replysWithEs) Hide(userId int64, id int64) error {
	err := r.dbHandle.Hide(userId, id)
	if err != nil {
		return err
	}
	toUpdate := map[string]interface{}{
		"see_status": model.Reply_See_Hide,
	}
	err = es.New().Update(IndexNameOfReplys, id, toUpdate)
	return err
}

func (r replysWithEs) Update(id int64, m model.Replys) error {
	err := r.dbHandle.Update(id, m)
	if err != nil {
		return err
	}
	mm, err := r.dbHandle.GetById(id)
	if err != nil {
		return err
	}
	esM := EsReplysModel{
		Replys: mm,
		TimeModelOfEs: model.TimeModelOfEs{
			Created: time.Time(mm.TimeModel.Created),
			Updated: time.Time(mm.TimeModel.Updated),
		},
	}
	// 用插入的接口, 懒得判断更新了
	return es.New().IndexDoc(IndexNameOfReplys, mm.Id, esM)
}

type ReplyQualityParam struct {
	Shares   float64 `json:"shares"`   // 分享次数比分
	Likes    float64 `json:"likes"`    // 点赞次数比分
	Comments float64 `json:"comments"` // 评论次数比分

}

func (r replysWithEs) ListByPostId(postId int64, pagination requests.Pagination) (ms []model.Replys, err error) {
	e := es.New()
	client, err := e.GetClient()
	if err != nil {
		return
	}
	var paramsStruct = ReplyQualityParam{}
	var params = map[string]interface{}{}
	_ = mapstructure.Decode(paramsStruct, &params)
	script := elastic.NewScript(`
	double score = doc['shares'].value*rates.shares+
		doc['likes'].value*rates.likes+
		doc['comments'].value*rates.comments
	return score
	`).Params(params)
	result, err := client.Search().Index(es.WithPrefix(IndexNameOfReplys)).
		Query(elastic.NewTermQuery("id", postId)).
		Query(elastic.NewFunctionScoreQuery().AddScoreFunc(elastic.NewScriptFunction(script))).
		From(pagination.GetOffset()).Size(pagination.Limit).
		SortBy(elastic.NewScoreSort().Order(false)).
		Do(context.Background())
	if err != nil {
		err = errcode.NewError(errcode.EsError, err)
		return
	}
	for _, item := range result.Each(reflect.TypeOf(EsReplysModel{})) {
		em := item.(EsReplysModel)
		m := em.Replys
		m.Created = common.Time(em.Created)
		m.Updated = common.Time(em.Updated)
		ms = append(ms, m)
	}
	return
}

func (r replysWithEs) SubListByReplyId(replyId int64, pagination requests.Pagination) (ms []model.Replys, err error) {
	e := es.New()
	client, err := e.GetClient()
	if err != nil {
		return
	}
	result, err := client.Search().Index(es.WithPrefix(IndexNameOfReplys)).
		Query(elastic.NewTermQuery("to_top_reply_id", replyId)).
		From(pagination.GetOffset()).Size(pagination.Limit).
		SortBy(elastic.NewFieldSort("created").Order(false)).
		Do(context.Background())
	if err != nil {
		err = errcode.NewError(errcode.EsError, err)
		return
	}
	for _, item := range result.Each(reflect.TypeOf(EsReplysModel{})) {
		em := item.(EsReplysModel)
		m := em.Replys
		m.Created = common.Time(em.Created)
		m.Updated = common.Time(em.Updated)
		ms = append(ms, m)
	}
	return
}

func (r replysWithEs) AddStat(id int64, statType model.ReplyStatType) error {
	err := r.dbHandle.AddStat(id, statType)
	if err != nil {
		return err
	}
	return es.New().Increase(IndexNameOfReplys, id, string(statType))
}

func (r replysWithEs) SubStat(id int64, statType model.ReplyStatType) error {
	err := r.dbHandle.SubStat(id, statType)
	if err != nil {
		return err
	}
	return es.New().Decrease(IndexNameOfReplys, id, string(statType))
}

type replys struct {
	db *xorm.Engine
}

func (r replys) GetLikedReplysIdsBatch(userId int64, replyIds ...int64) (map[int64]interface{}, error) {
	return NewLikes().GetLikeStatus(userId, model.LikesType_Reply, replyIds...)
}

type CountResp struct {
	ToTopReplyId int64
	Cnt          int
}

func (r replys) GetSubReplCountBatch(replyIds ...int64) ([]CountResp, error) {
	var countMs = []CountResp{}
	if len(replyIds) == 0 {
		return countMs, nil
	}
	var replyIdsStrs []string
	for _, id := range replyIds {
		replyIdsStrs = append(replyIdsStrs, strconv.FormatInt(id, 10))
	}
	var sql = fmt.Sprintf("select to_top_reply_id,count(1) as cnt from replys where to_top_reply_id in (%s)  group by to_top_reply_id", strings.Join(replyIdsStrs, ","))
	err := r.db.SQL(sql).Find(&countMs)
	if err != nil {
		return countMs, errcode.NewError(errcode.DaoFailed, err)
	}
	return countMs, nil
}

func (r replys) GetSubReplyListBatch(replyIds ...int64) ([]model.Replys, error) {
	var idMs = []model.Replys{}
	if len(replyIds) == 0 {
		return idMs, nil
	}
	var replyIdsStrs []string
	for _, id := range replyIds {
		replyIdsStrs = append(replyIdsStrs, strconv.FormatInt(id, 10))
	}
	var sql = fmt.Sprintf("select max(id) as id from replys where to_top_reply_id in (%s)  group by to_top_reply_id", strings.Join(replyIdsStrs, ","))
	err := r.db.SQL(sql).Find(&idMs)
	if err != nil {
		return idMs, errcode.NewError(errcode.DaoFailed, err)
	}
	var ids = []int64{}
	for _, idRow := range idMs {
		ids = append(ids, idRow.Id)
	}
	var ms = []model.Replys{}
	err = r.db.In("id", common.ToInterfaceSlice(ids)...).Find(&ms)
	if err != nil {
		return ms, errcode.NewError(errcode.DaoFailed, err)
	}
	return ms, nil
}

func (r replys) GetReplyedBefore(userId int64) ([]int64, error) {
	var rows []model.Replys
	var postIds []int64
	err := r.db.Where("user_id=?", userId).Cols("to_post_id").Find(&rows)
	if err != nil {
		return postIds, errcode.NewError(errcode.DaoFailed, err)
	}
	postIds = []int64{}
	for _, row := range rows {
		postIds = append(postIds, row.ToPostId)
	}
	return postIds, nil
}

func (r replys) GetById(id int64) (model.Replys, error) {
	var m model.Replys
	exist, err := r.db.ID(id).
		Where("see_status=?", model.Post_See_EveryBody).Get(&m)
	if err != nil {
		return m, errcode.NewError(errcode.DaoFailed, err)
	}
	if !exist {
		return m, errcode.NewError(errcode.RequestParamInvalid, err)
	}
	return m, nil
}

func (r replys) SubStat(id int64, statType model.ReplyStatType) error {
	_, err := r.db.Id(id).Decr(string(statType), 1).Update(&model.Replys{})
	if err != nil {
		return errcode.NewError(errcode.DaoFailed, err)
	}
	return nil
}

func (r replys) AddStat(id int64, statType model.ReplyStatType) error {
	_, err := r.db.Id(id).Incr(string(statType), 1).Update(&model.Replys{})
	if err != nil {
		return errcode.NewError(errcode.DaoFailed, err)
	}
	return nil
}

func (r replys) SubListByReplyId(replyId int64, pagination requests.Pagination) ([]model.Replys, error) {
	var (
		ms  = []model.Replys{}
		err error
	)
	err = r.db.Where("to_top_reply_id=?", replyId).OrderBy("updated desc").
		Limit(pagination.Limit, pagination.GetOffset()).Find(&ms)
	if err != nil {
		return ms, errcode.NewError(errcode.DaoFailed, err)
	}
	return ms, err
}

func (r replys) ListByPostId(postId int64, pagination requests.Pagination) ([]model.Replys, error) {
	var (
		ms  = []model.Replys{}
		err error
	)
	err = r.db.Where("to_post_id=?", postId).
		Where("to_top_reply_id=?", model.ReplyToPost).
		Limit(pagination.Limit, pagination.GetOffset()).
		Find(&ms)
	if err != nil {
		return ms, errcode.NewError(errcode.DaoFailed, err)
	}
	return ms, err

}

func (r replys) GetByIds(ids ...int64) (map[int64]model.Replys, error) {
	var ps []model.Replys
	var psMap = map[int64]model.Replys{}

	err := r.db.In("id", ids).
		Where("see_status=?", model.Post_See_EveryBody).Find(&ps)
	if err != nil {
		return psMap, errcode.NewError(errcode.DaoFailed, err)
	}

	for _, pp := range ps {
		psMap[pp.Id] = pp
	}
	return psMap, err
}

func (r replys) Insert(m model.Replys) (model.Replys, error) {
	_, err := r.db.InsertOne(m)
	if err != nil {
		return m, errcode.NewError(errcode.DaoFailed, err)
	}
	return m, nil
}

func (r replys) Hide(userId int64, id int64) error {
	var m = model.Replys{
		SeeStatus: model.Reply_See_Hide,
	}
	_, err := r.db.Where("id=?", id).Where("user_id=?", userId).Update(m)
	if err != nil {
		return errcode.NewError(errcode.DaoFailed, err)
	}
	return nil
}

func (r replys) Update(id int64, m model.Replys) error {
	_, err := r.db.Where("id=?", id).Update(m)
	if err != nil {
		return errcode.NewError(errcode.DaoFailed, err)
	}
	return nil
}
